package jp.co.nes.awssupport.business.logic;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.YearMonth;
import java.time.ZonedDateTime;

import jp.co.nes.awssupport.business.utility.TargetAmountHelper;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.ContractInformationBean;
import jp.co.nes.awssupport.common.bean.MonthlyCompiledBillingBean;
import jp.co.nes.awssupport.common.bean.PremiumSupportAssignedBean;
import jp.co.nes.awssupport.common.constant.SupportPlan;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * プレミアムサポート処理です。
 */
public class PremiumSupportProcessing {

    /** プレミアムサポート料金を計算する際に使用する閾値(120万) */
    private static final long THRESHOLD_AMOUNT1 = 1200000L;

    /** プレミアムサポート料金を計算する際に使用する閾値(960万) */
    private static final long THRESHOLD_AMOUNT2 = 9600000L;

    /** プレミアムサポート料金を計算する際に使用する閾値(3000万) */
    private static final long THRESHOLD_AMOUNT3 = 30000000L;

    /** プレミアムサポート料金を計算する際に使用する比率(8%) */
    private static final BigDecimal RATE1 = new BigDecimal("0.08");

    /** プレミアムサポート料金を計算する際に使用する比率(6%) */
    private static final BigDecimal RATE2 = new BigDecimal("0.06");

    /** プレミアムサポート料金を計算する際に使用する比率(5%) */
    private static final BigDecimal RATE3 = new BigDecimal("0.05");

    /** プレミアムサポート料金を計算する際に使用する比率(3%) */
    private static final BigDecimal RATE4 = new BigDecimal("0.03");

    /** プレミアムサポート料金の最低金額(1万) */
    private static final long MIN_PREMIUM = 10000L;

    /** 処理対象年月 */
    private YearMonth targetYearMonth;

    /** 契約情報管理 */
    private ContractInformationManage ciManage;

    /**
     * 処理対象年月を引数にもつコンストラクタです。
     *
     * @param targetYearMonth 処理対象年月
     */
    public PremiumSupportProcessing(YearMonth targetYearMonth) {
        this.targetYearMonth = targetYearMonth;
        ciManage = new ContractInformationManage(targetYearMonth);
    }

    /**
     * プレミアムサポート料金を計算し、プレミアムサポート適用を返却します。<br>
     * 【補足事項】
     * <ul>
     * <li>
     * 月次集計結果に含まれる請求額（税抜）は、小数第4位で四捨五入して小数第3位にした値の合計を、<br>
     * 小数第1位で四捨五入して整数にした値である必要があります。
     * </li>
     * </ul>
     *
     * @param mcbBean 月次集計結果
     * @return プレミアムサポート適用(契約情報のサポートプランがプレミアムでない場合はnullを返却)
     */
    public PremiumSupportAssignedBean apply(MonthlyCompiledBillingBean mcbBean) {
        String linkedAccountId = mcbBean.getLinkedAccountId();
        ContractInformationBean ciBean = ciManage.get(linkedAccountId);
        if (ciBean == null) {
            throw new CommonException("contract information not exist(linkedAccountId=" + linkedAccountId + ")");
        }

        if (!SupportPlan.PREMIUM.getSforceValue().equals(ciBean.getSupportPlan())) {
            return null;
        }

        long billingAmount = mcbBean.getBillingAmount().longValueExact();
        TargetAmountHelper helper = new TargetAmountHelper(linkedAccountId, targetYearMonth);
        long targetAmount = helper.getTargetAmountOfMeAndPs(billingAmount);

        PremiumSupportAssignedBean applyBean = new PremiumSupportAssignedBean();
        applyBean.setLinkedAccountId(linkedAccountId);
        applyBean.setYearMonth(DateUtil.toInt(targetYearMonth));

        ZonedDateTime startDate = ciBean.getStartDate();
        long premiumSupportAmount = premiumSupportAmount(targetAmount, targetYearMonth, startDate);

        applyBean.setTargetAmount(targetAmount);
        applyBean.setPremiumSupportAmount(premiumSupportAmount);
        return applyBean;
    }

    /**
     * 引き渡された請求額（税抜）に対応するプレミアムサポート料金を返却します。<br>
     * なお、引き渡される請求額（税抜）は、月次集計結果を連結アカウントID,年月で集計した内容です。<br>
     * 集計方法は、小数第4位で四捨五入して小数第3位にした値の合計を、小数第1位で四捨五入して整数にします。
     *
     * @param billingAmount 請求額（税抜）
     * @param targetYearMonth 処理対象年月
     * @param startDate 契約開始日付
     * @return プレミアムサポート料金
     */
    private long premiumSupportAmount(long billingAmount, YearMonth targetYearMonth, ZonedDateTime startDate) {
        long amount1 = 0L;
        long amount2 = 0L;
        long amount3 = 0L;
        long amount4 = 0L;

        if (billingAmount <= THRESHOLD_AMOUNT1) {
            amount1 = billingAmount;
        } else if (billingAmount > THRESHOLD_AMOUNT1 && billingAmount <= THRESHOLD_AMOUNT2) {
            amount1 = THRESHOLD_AMOUNT1;
            amount2 = billingAmount - THRESHOLD_AMOUNT1;
        } else if (billingAmount > THRESHOLD_AMOUNT2 && billingAmount <= THRESHOLD_AMOUNT3) {
            amount1 = THRESHOLD_AMOUNT1;
            amount2 = THRESHOLD_AMOUNT2 - THRESHOLD_AMOUNT1;
            amount3 = billingAmount - THRESHOLD_AMOUNT2;
        } else if (billingAmount > THRESHOLD_AMOUNT3) {
            amount1 = THRESHOLD_AMOUNT1;
            amount2 = THRESHOLD_AMOUNT2 - THRESHOLD_AMOUNT1;
            amount3 = THRESHOLD_AMOUNT3 - THRESHOLD_AMOUNT2;
            amount4 = billingAmount - THRESHOLD_AMOUNT3;
        } else {
            // 通常は発生しない。
            throw new CommonException("billingAmount is wrong");
        }

        BigDecimal work1 = (new BigDecimal(amount1)).multiply(RATE1);
        BigDecimal work2 = (new BigDecimal(amount2)).multiply(RATE2);
        BigDecimal work3 = (new BigDecimal(amount3)).multiply(RATE3);
        BigDecimal work4 = (new BigDecimal(amount4)).multiply(RATE4);
        long premium = work1.add(work2).add(work3).add(work4).setScale(0, RoundingMode.HALF_UP).longValueExact();
        if (premium < MIN_PREMIUM) {
            premium = MIN_PREMIUM;
        }

        YearMonth startYearMonth = DateUtil.toYearMonth(startDate);
        if (startYearMonth.compareTo(targetYearMonth) == 0) {
            int dayOfMonth = startDate.getDayOfMonth();
            if (dayOfMonth != 1) {
                // 契約開始日の年月 == 処理対象年月の場合でかつ契約開始日の日が2以上だった場合は、
                // 以下の内容で、日割計算を実施する。
                // ・(計算したプレミアムサポート料金 / 処理対象年月の月末日)(小数第1位で四捨五入し整数にする) * (処理対象年月の月末日 - 契約開始日の日 + 1));
                long lengthOfMonth = targetYearMonth.lengthOfMonth();
                BigDecimal workPremium = new BigDecimal(premium);
                BigDecimal workLengthOfMonth = new BigDecimal(lengthOfMonth);
                long workValue01 = workPremium.divide(workLengthOfMonth, 0, BigDecimal.ROUND_HALF_UP).longValueExact();
                long workValue02 = lengthOfMonth - dayOfMonth + 1;
                premium = workValue01 * workValue02;
            }
        } else if (startYearMonth.compareTo(targetYearMonth) < 0) {
            // 契約開始日の年月 < 処理対象年月の場合は、日割計算不要なので、何もしない
        } else if (startYearMonth.compareTo(targetYearMonth) > 0) {
            // 契約開始日 > 処理対象年月の場合は例外をスローする(通常は発生しない)。
            throw new CommonException("startDate or targetYearMonth wrong");
        }

        return premium;
    }
}
